/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    disp_manager.c
 * @brief   显示设备管理者文件
 * @author  K
 * @version 0.0.1
 * @date    2021-07-02
 * @license MulanPSL-1.0
 *
 * 文件修改历史：
 * <时间>       | <版本>    | <作者>  | <描述>
 * 2021-07-02   | v0.0.1    | Kcode   | 显示设备管理者文件
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <stdlib.h>
#include <linux/videodev2.h>

#include "config.h"
#include "disp_manager.h"
#include "debug_manager.h"
#include "string.h"

static PT_DispOpr s_ptDispOprHead;      /**< 显示设备链表头 */
static PT_DispOpr s_ptDefaultDispOpr; /**< 默认显示设备 */
static PT_VideoMem s_ptVieoMemHead;     /**< 页面内存链表头 */

/*!
 * @brief  注册"显示模块", 把所能支持的显示设备的操作函数放入链表
 * @param  ptDispOpr[in]  一个结构体,内含显示设备的操作函数
 * @return 0：注册成功，其他值：失败
 */
int RegisterDispOpr(PT_DispOpr ptDispOpr)
{
	PT_DispOpr ptTmp;

	if (!s_ptDispOprHead)
	{
		s_ptDispOprHead   = ptDispOpr;
		ptDispOpr->ptNext = NULL;
	}
	else
	{
		ptTmp = s_ptDispOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptDispOpr;
		ptDispOpr->ptNext = NULL;
	}

	return 0;
}

/*!
 * @brief  显示本程序能支持的"显示模块"
 * @param  [in] 无 [out]无
 * @return 无
 */
void ShowDispOpr(void)
{
	int i = 0;
	PT_DispOpr ptTmp = s_ptDispOprHead;

	while (ptTmp)
	{
		printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
}

/*!
 * @brief  根据名字取出指定的"显示模块"
 * @param  pName[in] 名字
 * @return NULL：失败,没有指定的模块, 非NULL：显示模块的PT_DispOpr结构体指针
 */
PT_DispOpr GetDispOpr(char *pName)
{
	PT_DispOpr ptTmp = s_ptDispOprHead;
	
	while (ptTmp) {
		if (strcmp(ptTmp->name, pName) == 0)
			return ptTmp;
			
		ptTmp = ptTmp->ptNext;
	}
	
	return NULL;
}

/*!
 * @brief  根据名字取出指定的"显示模块", 调用它的初始化函数, 并且清屏
 * @param  name[in] 名字
 * @return 无
 */
void SelectAndInitDefaultDispDev(char *name)
{
	s_ptDefaultDispOpr = GetDispOpr(name);
	if (s_ptDefaultDispOpr == NULL) {
		DebugPrint(APP_ERR"s_ptDefaultDispOpr is NULL! File:%s Line:%d\n", __FILE__, __LINE__);
		return ;
	}

	s_ptDefaultDispOpr->DeviceInit();
	s_ptDefaultDispOpr->CleanScreen(0);
}

/*!
 * @brief  显示模块清理工作
 * @param  [in] 无
 * @return 无
 */
void ExitDispDev(void)
{
    if (s_ptDefaultDispOpr == NULL) {
		DebugPrint(APP_ERR"s_ptDefaultDispOpr is NULL! File:%s Line:%d\n", __FILE__, __LINE__);
		return ;
	}

	s_ptDefaultDispOpr->DeviceExit();
}

/*!
 * @brief  获得显示设备的显存, 在该显存上操作就可以直接在LCD上显示出来
 * @param  [in] 无
 * @return 无
 */
PT_VideoMem GetDevVideoMem(void)
{
	PT_VideoMem ptTmp = s_ptVieoMemHead;
	
	while (ptTmp)
	{
		if (ptTmp->isDevFB)
		{
			return ptTmp;
		}
		ptTmp = ptTmp->ptNext;
	}
	return NULL;
}

/*!
 * @brief  获得显示设备的显存, 在该显存上操作就可以直接在LCD上显示出来
 * @param  [in] 无
 * @return 无
 */
int GetVideoBufForDisplay(PT_VIDEOBUF framebuf)
{
    framebuf->pixel_format = (s_ptDefaultDispOpr->Bpp == 16) ? V4L2_PIX_FMT_RGB565 :\
                              (s_ptDefaultDispOpr->Bpp == 32) ? V4L2_PIX_FMT_RGB32 : 0;
                              
    framebuf->pixel_data.width       = s_ptDefaultDispOpr->Xres;
    framebuf->pixel_data.height      = s_ptDefaultDispOpr->Yres;
    framebuf->pixel_data.bpp         = s_ptDefaultDispOpr->Bpp;
    framebuf->pixel_data.linebytes  = s_ptDefaultDispOpr->LineWidth;
    framebuf->pixel_data.TotalBytes = framebuf->pixel_data.height * framebuf->pixel_data.linebytes;
    framebuf->pixel_data.PixelDatas = s_ptDefaultDispOpr->pDispMem;

    return 0;
}

/*!
 * @brief  程序事先用SelectAndInitDefaultDispDev选择了显示模块,本函数返回该显示模块
 * @return 显示模块的PT_DispOpr结构体指针
 */
PT_DispOpr GetDefaultDispDev(void)
{
	return s_ptDefaultDispOpr;
}

/*!
 * @brief  获得所使用的显示设备的分辨率和BPP
 * @param  pXres[out] 存X分辨率
 * @param  pYres[out] 存Y分辨率
 * @param  pbpp[out]  存bpp
 * @return 0：成功，-1：失败(未使用SelectAndInitDefaultDispDev来选择显示模块)
 */
int GetDispResolution(int *pXres, int *pYres, int *pbpp)
{
	if (s_ptDefaultDispOpr) {
		*pXres = s_ptDefaultDispOpr->Xres;
		*pYres = s_ptDefaultDispOpr->Yres;
		*pbpp  = s_ptDefaultDispOpr->Bpp;
		return 0;
	} else
		return -1;
}


/*!
 * @brief  VideoMem: 为加快显示速度,我们事先在缓存中构造好显示的页面的数据,
 *         (这个缓存称为VideoMem)显示时再把VideoMem中的数据复制到设备的显存上
 * @param  num[in] 所要分配的内存数量
 * @return 0：成功，-1：失败(未使用SelectAndInitDefaultDispDev来选择显示模块)
 */
int AllocVideoMem(int num)
{
	int i;
	int bpp;
	int linebytes;
	int xres, yres;
	int VMSize;
	PT_VideoMem ptNew;
	PT_VideoMem ptTmp;

	bpp = yres = xres = 0;
	
	GetDispResolution(&xres, &yres, &bpp);
	VMSize    = xres * yres * bpp / 8;
	linebytes = xres * bpp / 8;
	
	/* 1、先把设备本身的Framebuffer放入链表 */
	ptNew = (PT_VideoMem)malloc(sizeof(T_VideoMem));
	if (ptNew == NULL) {
		DebugPrint(APP_ERR"Framebuffer set fail!\n");
		goto fail;
	}

	/* 设置该内存所描述页面的信息 */
	ptNew->id 					  = 0;
	ptNew->isDevFB 				  = 1;
	ptNew->eVideoMemState         = VMS_FREE;
	ptNew->ePicState              = PS_BLANK;
	ptNew->tPixelDatas.bpp        = bpp;
	ptNew->tPixelDatas.height     = yres;
	ptNew->tPixelDatas.width      = xres;
	ptNew->tPixelDatas.linebytes  = linebytes;		
	ptNew->tPixelDatas.PixelDatas = s_ptDefaultDispOpr->pDispMem;
	ptNew->tPixelDatas.TotalBytes = VMSize;

	/* 强制设置设备本身的Framebuffer状态为被占用 */
	if (num != 0)
		ptNew->eVideoMemState = VMS_USED_FOR_CURMAIN;

	/* 头插法插入链表 */
	ptNew->ptNext = s_ptVieoMemHead;
	s_ptVieoMemHead = ptNew;

	/* 2、后分配用于页面管理的内存并设置结构体，组成：页面描述 + 显存 */
	for (i = 0; i < num; i++) {
		ptNew = (PT_VideoMem)malloc(sizeof(T_VideoMem) + VMSize);
		if (ptNew == NULL) {
			DebugPrint(APP_ERR"ptNew malloc fail，already malloc num: %d!\n", i);
			goto fail;
		}

		/* 设置该内存所描述页面的信息 */
		ptNew->id 					  = 0;
		ptNew->isDevFB 				  = 0;
		ptNew->eVideoMemState         = VMS_FREE;
		ptNew->ePicState              = PS_BLANK;
		ptNew->tPixelDatas.bpp        = bpp;
		ptNew->tPixelDatas.height     = yres;
		ptNew->tPixelDatas.width      = xres;
		ptNew->tPixelDatas.linebytes  = linebytes;		
		ptNew->tPixelDatas.PixelDatas = (unsigned char *)(ptNew + 1);
		ptNew->tPixelDatas.TotalBytes = VMSize;

		/* 头插法插入链表 */
		ptNew->ptNext = s_ptVieoMemHead;
		s_ptVieoMemHead = ptNew;
	}

	return 0;
	
fail:
	while (s_ptVieoMemHead) {
		ptTmp = s_ptVieoMemHead->ptNext;
		free(s_ptVieoMemHead);
		s_ptVieoMemHead = ptTmp;
	}

	return -1;
}

/*!
 * @brief  获得一块可操作的VideoMem(它用于存储要显示的数据), 用完后用PutVideoMem来释放
 * @param  id[in] ID值,先尝试在众多VideoMem中找到ID值相同的
 * @param  isCurMain[in] 1表示当前程序马上要使用VideoMem,无法如何都要返回一个VideoMem
 *                       0表示这是为了改进性能而提前取得VideoMem,不是必需的
 * @return NULL：失败，没有可用的VideoMem，非NULL：成功,返回PT_VideoMem结构体
 */
PT_VideoMem GetVideoMem(int id, int isCurMain)
{
	PT_VideoMem ptTmp = s_ptVieoMemHead;

	/* 1. 优先: 取出空闲的、ID相同的videomem */
	while (ptTmp)
	{
		if ((ptTmp->eVideoMemState == VMS_FREE) && (ptTmp->id == id))
		{
			ptTmp->eVideoMemState = isCurMain ? VMS_USED_FOR_CURMAIN : VMS_USED_FOR_PREPARE;
			return ptTmp;
		}
		ptTmp = ptTmp->ptNext;
	}

	/* 2. 如果前面不成功, 取出一个空闲的并且ptVideoMem->ePicState = PS_BLANK的videomem */
	ptTmp = s_ptVieoMemHead;
	while (ptTmp)
	{
		if ((ptTmp->eVideoMemState == VMS_FREE) && (ptTmp->ePicState == PS_BLANK))
		{
			ptTmp->id = id;
			ptTmp->eVideoMemState = isCurMain ? VMS_USED_FOR_CURMAIN : VMS_USED_FOR_PREPARE;
			return ptTmp;
		}
		ptTmp = ptTmp->ptNext;
	}	
	
	/* 3. 如果前面不成功: 取出任意一个空闲的videomem */
	ptTmp = s_ptVieoMemHead;
	while (ptTmp)
	{
		if (ptTmp->eVideoMemState == VMS_FREE)
		{
			ptTmp->id = id;
			ptTmp->ePicState = PS_BLANK;
			ptTmp->eVideoMemState = isCurMain ? VMS_USED_FOR_CURMAIN : VMS_USED_FOR_PREPARE;
			return ptTmp;
		}
		ptTmp = ptTmp->ptNext;
	}

    /* 4. 如果没有空闲的videomem并且isCurMain为1, 则取出任意一个videomem(不管它是否空闲) */
    if (isCurMain)
    {
    	ptTmp = s_ptVieoMemHead;
    	ptTmp->id = id;
    	ptTmp->ePicState = PS_BLANK;
    	ptTmp->eVideoMemState = isCurMain ? VMS_USED_FOR_CURMAIN : VMS_USED_FOR_PREPARE;
    	return ptTmp;
    }
    
	return NULL;
}

/*!
 * @brief  使用GetVideoMem获得的VideoMem, 用完时用PutVideoMem释放掉
 * @param  ptVideoMem[in] 使用完毕的VideoMem
 * @return 无
 */
void PutVideoMem(PT_VideoMem ptVideoMem)
{
	ptVideoMem->eVideoMemState = VMS_FREE;
    if (ptVideoMem->id == -1)
    {
        ptVideoMem->ePicState = PS_BLANK;
    }
}


/*!
 * @brief  把VideoMem中内存全部清为某种颜色
 * @param  ptVideoMem[in] VideoMem结构体指针, 内含要操作的内存
 * @param  dwColor[in]    设置为该颜色
 * @return 无
 */
void ClearVideoMem(PT_VideoMem ptVideoMem, unsigned int color)
{
	int i;
	unsigned char  *pPen8;
	unsigned short *pPen16;
	unsigned int   *pPen32;
	unsigned int 	color16bpp;
	unsigned int	red, green, blue;

	pPen8  = ptVideoMem->tPixelDatas.PixelDatas;
	pPen16 = (unsigned short *)pPen8;
	pPen32 = (unsigned int   *)pPen8;

	switch (ptVideoMem->tPixelDatas.bpp) {
	case 8:
		memset(pPen8, color, ptVideoMem->tPixelDatas.TotalBytes);
		break;
	case 16:
		/* RGB:565 */
		red      = ((color >> 16) & 0xffff) >> 3;
		green    = ((color >> 8 )  & 0xffff) >> 2;
		blue     = ((color >> 0 )  & 0xffff) >> 3;
		color16bpp    = (red << 11) | (green << 5) | blue;
		
		for (i = 0; i < ptVideoMem->tPixelDatas.TotalBytes;) {
			*pPen16 = color16bpp;
			pPen16++;
			i += 2;
		}
		break;
	case 32:
		for (i = 0; i < ptVideoMem->tPixelDatas.TotalBytes;) {
			*pPen32 = color;
			pPen32++;
			i += 4;
		}
		break;
	default:
		DBG_PRINTF("can not surport %dbpp\n", ptVideoMem->tPixelDatas.bpp);
		break;
	}

}

/*!
 * @brief  刷新像素数据到设备职工
 * @param  pixel_data[in] 像素数据
 * @return 无
 */
void FlushPixelDatasToDev(PT_PIXELDATAS pixel_data)
{
    s_ptDefaultDispOpr->ShowPage(pixel_data);  
}

/*!
 * @brief  把VideoMem中内存的指定区域全部清为某种颜色
 * @param  ptVideoMem[in] VideoMem结构体指针, 内含要操作的内存
 * @param  ptLayout[in]   矩形区域, 指定了左上角,右在角的坐标
 * @param  dwColor[in]    设置为该颜色
 * @return 无
 */
void ClearVideoMemRegion(PT_VideoMem ptVideoMem, 
                    PT_Layout ptLayout, unsigned int dwColor)
{
	unsigned char *pucVM;
	unsigned short *pwVM16bpp;
	unsigned int *pdwVM32bpp;
	unsigned short wColor16bpp; /* 565 */
	int iRed;
	int iGreen;
	int iBlue;
	int iX;
	int iY;
    int linebytesClear;
    int i;

	pucVM	   = ptVideoMem->tPixelDatas.PixelDatas + ptLayout->TopLeftY * ptVideoMem->tPixelDatas.linebytes + ptLayout->TopLeftX * ptVideoMem->tPixelDatas.bpp / 8;
	pwVM16bpp  = (unsigned short *)pucVM;
	pdwVM32bpp = (unsigned int *)pucVM;

    linebytesClear = (ptLayout->BotRightX - ptLayout->TopLeftX + 1) * ptVideoMem->tPixelDatas.bpp / 8;

	switch (ptVideoMem->tPixelDatas.bpp)
	{
		case 8:
		{
            for (iY = ptLayout->TopLeftY; iY <= ptLayout->BotRightY; iY++)
            {
    			memset(pucVM, dwColor, linebytesClear);
                pucVM += ptVideoMem->tPixelDatas.linebytes;
            }
			break;
		}
		case 16:
		{
			iRed   = (dwColor >> (16+3)) & 0x1f;
			iGreen = (dwColor >> (8+2)) & 0x3f;
			iBlue  = (dwColor >> 3) & 0x1f;
			wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;
            for (iY = ptLayout->TopLeftY; iY <= ptLayout->BotRightY; iY++)
            {
                i = 0;
                for (iX = ptLayout->TopLeftX; iX <= ptLayout->BotRightX; iX++)
    			{
    				pwVM16bpp[i++]	= wColor16bpp;
    			}
                pwVM16bpp = (unsigned short *)((unsigned int)pwVM16bpp + ptVideoMem->tPixelDatas.linebytes);
            }
			break;
		}
		case 32:
		{
            for (iY = ptLayout->TopLeftY; iY <= ptLayout->BotRightY; iY++)
            {
                i = 0;
                for (iX = ptLayout->TopLeftX; iX <= ptLayout->BotRightX; iX++)
    			{
    				pdwVM32bpp[i++]	= dwColor;
    			}
                pdwVM32bpp = (unsigned int *)((unsigned int)pdwVM32bpp + ptVideoMem->tPixelDatas.linebytes);
            }
			break;
		}
		default :
		{
			DBG_PRINTF("can't support %d bpp\n", ptVideoMem->tPixelDatas.bpp);
			return;
		}
	}

}

/*!
 * @brief  注册显示设备
 * @param  [in] 无
 * @return 0：成功，其他值：失败
 */
int DisplayInit(void)
{
	int error;
	
	error = FBInit();
    error |= CRTInit();

	return error;
}

